<?php
namespace app\api\logic;

use app\api\controller\WxNotify;

class Weixin extends Base {
    protected $appid;           ##微信ID
    protected $secret;          ##微信秘钥
    protected $isonlyopenid;    ##是否静默授权
    protected $notifyUrl;       ##微信支付回调URL
    protected $token;           ##令牌
    protected $encodingAesKey;  ##消息加密密钥
    protected $timeStamp;       ##当前时间戳
    protected $nonce;           ##随机数
    protected $accessKey;       ##入口验证KEY

    function __construct($appid = '', $secret = '', $isonlyopenid = false) {
        $this->accessKey = config('custom.accessKey');
        $this->timeStamp = time();
        $this->nonce = mt_rand(10000, 99999);
        $this->isonlyopenid = $isonlyopenid;
        $this->notifyUrl = 'https://sndzza.5iape.com/api/weixin/wxNotify';
    }

    /**
     * 微信支付参数配置
     * @param number $admin_id      客户ID
     * @param string $type          类型 - 默认为小程序: applet，公众号: gzh
     */
    public function wxPayConfig($admin_id) {
        $field = 'id, appid, secret, pay_mchid, pay_key';
        $res = db('customer')->where(['id' => $admin_id])->field($field)->find();

        if (!$res) {
            err('ACCOUNT NULL');
        }
                
        $appid = $res['appid'];
        $secret = $res['secret'];
        
        // 公众号和小程序使用同一个商户
        $pay_mchid = $res['pay_mchid'];
        $pay_secret = $res['pay_key'];

        define('CUS_WX_APPID', $appid);
        define('CUS_WX_APPSECRET', $secret);
        define('CUS_WX_MCHID', $pay_mchid);
        define('CUS_WX_KEY', $pay_secret);
        define('CUS_WX_SSLCERT', VENDOR_PATH . 'Wxpay/cert/' . $res['id'] . '_cert.pem');   // ../cert/apiclient_cert.pem
        define('CUS_WX_SSLKEY', VENDOR_PATH . 'Wxpay/cert/' . $res['id'] . '_key.pem');    // ../cert/apiclient_key.pem
    }

    /**
     * 微信小程序 - 注册/非第一次登陆
     * 
     * @param string $cid               客户唯一ID - sha1加密参数
     * @param string $js_code           根据js_code换取session_key和openid
     * @param string $encrypted_data    用户授权加密数据
     * @param string $iv                用户授权key
     * @return json - mstr、mtime、openid
     */
    public function loginApplet($userinfo) {
        // 查询客户ID是否存在
        $info = db('customer')->where(['sign' => $userinfo['cid']])->field('id, appid, secret')->find();
        //$info = db('customer')->where(['sign' => '213a339ff29f2d279dga43519ffac9bf1ac'])->field('id, appid, secret')->find();
        if (!$info) {
            err('PARAM ERROR');
        }

        // 访问微信接口获取session_key/openid
        $url = 'https://api.weixin.qq.com/sns/jscode2session?'
            . 'appid=' . $info['appid']
            . '&secret=' . $info['secret']
            . '&js_code=' . $userinfo['js_code']
            . '&grant_type=authorization_code';

        $response = curlGet($url);

        $arr = json_decode($response, true);

        /* if ($info['id'] == 1001) {
            dump($info);
            dump($arr);
            die;
        } */
        
        if (!isset($arr['openid'])) {
            err('GET SESSION KEY ERROR', $arr);
        }

        //$arr['openid'] = '2t0oIcbu0taBopSqLoNCEQ==';
        //$arr['session_key'] = '';

        // 查询用户是否注册过 - 如注册过直接返回用户信息
        $user = db('user')->where(['admin_id' => $info['id'], 'openid' => $arr['openid']])->find();

        // 自定义session_key
        $custom_session_key = sha1($this->timeStamp . $arr['openid'] . $userinfo['js_code'] . $this->nonce);

        $data = [];
        $data['session_key'] = $arr['session_key'];
        //$data['custom_session_key'] = $custom_session_key;
        $data['update_time'] = $this->timeStamp;

        // 昵称为空时才插入用户unitid、昵称、头像等信息
        if (!empty($userinfo['encrypted_data']) and !empty($userinfo['iv'])) {
            // 昵称为空时才更新
            
            include_once EXTEND_PATH .'WxbizDataCrypt/wxBizDataCrypt.php';
            $pc = new \WXBizDataCrypt($info['appid'], $arr['session_key']);
            $errCode = $pc->decryptData($userinfo['encrypted_data'], $userinfo['iv'], $resData);

            if ($errCode != 0) {
                err('encrtpted_data error', $errCode);
            }

            $resArr = json_decode($resData, true);

            if (isset($resArr['unionId'])) {
                $data['unionid'] = $resArr['unionId'];
            }

            if (empty($user['nickname'])) {
                $data['nickname'] = removeEmoji($resArr['nickName']);
            }

            if (empty($user['avatarurl'])) {
                $data['avatarurl'] = $resArr['avatarUrl'];
            }

            if (empty($user['gender'])) {
                $data['gender'] = $resArr['gender'];
            }
            
            if (empty($user['country'])) {
                $data['country'] = $resArr['country'];
            }
            
            if (empty($user['province'])) {
                $data['province'] = $resArr['province'];
            }
            
            if (empty($user['city'])) {
                $data['city'] = $resArr['city'];
            }
        }

        // 如果账号存在，更新的数据
        if ($user) {
            db('user')->where(['id' => $user['id']])->update($data);

            // 入口加密处理
            $mstr = accessEncrypt($info['id'], $user['id'], $this->timeStamp, $info['secret'], $this->accessKey);

            suc('success', ['mstr' => $mstr, 'mstr_time' => $this->timeStamp, 'openid' => $arr['openid']]);
        }

        // 如果没有注册 - 就插入数据
        $data['admin_id'] = $info['id'];
        $data['openid'] = $arr['openid'];
        $data['create_time'] = $this->timeStamp;
        
        $data['day'] = date("Y-m-d");
        $userId = db('user')->insertGetId($data);

        $mstr = accessEncrypt($info['id'], $userId, $this->timeStamp, $info['secret'], $this->accessKey);

        suc('success', ['mstr' => $mstr, 'mstr_time' => $this->timeStamp, 'openid' => $arr['openid']]);
    }

    /**
     * 无授权信息登陆 - 用手机号来区分账号
     *
     * @param number $mobile             手机号
     * @param number $cid                自定义客户ID
     * @return json - mstr、mstr_time
     */
    public function loginMobile($info) {
        // 查询客户ID是否存在
        $custom = db('customer')->where(['sign' => $info['cid']])->field('id, appid, secret')->find();

        if (!$custom) {
            err('CID PARAM ERROR');
        }

        $user = db('user')->where(['admin_id' => $custom['id'], 'mobile' => $info['mobile']])->find();

        // 注册过
        if ($user) {
            $mstr = accessEncrypt($custom['id'], $user['id'], $this->timeStamp, $custom['secret'], $this->accessKey);
            suc('success', ['mstr' => $mstr, 'mstr_time' => $this->timeStamp]);
        }

        // 手机号必须唯一
        $res = db('user')->where(['admin_id' => $custom['id'], 'mobile' => $info['mobile']])->count();

        if ($res > 0) {
            err('MOBILE EXIST');
        }

        $data = [];
        $data['invite_code'] = $info['invite_code'];    // 邀请码
        $data['mobile'] = $info['mobile'];
        $data['admin_id'] = $custom['id'];
        $data['update_time'] = $this->timeStamp;
        $data['create_time'] = $this->timeStamp;
        $data['invite_code'] = isset($info['invite_code']) ? $info['invite_code'] : '';
        $userId = db('user')->insertGetId($data);

        // 本地贷 - 写入邀请记录
        if (isset($info['classify'])) {
        
            if ($info['classify'] == 'ben_di_dai' and !empty($info['invite_code'])) {
                $data = [];
                $data['admin_id'] = $custom['id'];
                $data['user_id'] = $userId;
                $data['invite_code'] = $info['invite_code'];
                $data['create_time'] = date('Y-m-d H:i:s');
                $res = db('loan_invite_log')->insert($data);
            }
        }
        
        $mstr = accessEncrypt($custom['id'], $userId, $this->timeStamp, $custom['secret'], $this->accessKey);
        suc('success', ['mstr' => $mstr, 'mstr_time' => $this->timeStamp]);
    }

    /**
     * 获取用户信息
     * @param string $user_id       用户ID
     * @return array
     */
    public function getUserinfo($user_id) {
        $fields = 'id, openid, custom_session_key, avatarurl, nickname, city';
        $result = db('user')->field($fields)->where(['id' => $user_id])->find();

        if (!$result) {
            err('ID ERROR');
        }

        return $result;
    }

    /**
     * 验证自定义session_key是否存在
     * @param string $str
     */
    public function verifySessionKey($str) {
        if (strlen($str) != 40) {
            err('param error');
        }

        $result = db('user')->where(['custom_session_key' => $str])->find();

        if (!$result) {
            err('SESSION KEY ERROR');
        }

        suc();
    }

    /**
     * 获取openid等信息
     * @param   $user_id        用户ID
     * @return array
     */
    public function getOpenid($user_id) {
        $result = db('user')->field('openid')->where(['id' => $user_id])->find();

        if (!$result) {
            err('OPENID ERROR');
        }

        return $result['openid'];
    }

    /**
     * 小程序支付
     * 流程参考：https://pay.weixin.qq.com/wiki/doc/api/wxa/wxa_api.php?chapter=7_4&index=2
     * @param array $dbInfo     写入数据库的数据，如admin_id, user_id, price, quantity, subject
     * @param array $payInfo    处理订单是需要判断的数据
     * @return array
     */
    public function unifiedOrder($dbInfo, $payInfo = []) {
        # 金额不能为空 - 为空会导致签名失败
        if ($dbInfo['amount'] <= 0) {
            err('AMOUNT NULL');
        }

        # 引入配置参数
        self::wxPayConfig($dbInfo['admin_id']);

        require_once VENDOR_PATH . 'Wxpay/lib/WxPay.Api.php';

        # 获取openid
        $openid = $this->getOpenid($dbInfo['user_id']);

        # 订单号处理
        if (isset($payInfo['not_create_order'])) {
            // 处理未付款的订单 - 已经创建订单数据，cart表已经写入订单号
            $orderId = $dbInfo['out_trade_no'];
        } else {
            // 正常订单
            $orderId = date('YmdHis') . $dbInfo['user_id'] . mt_rand(100000, 999999);
        }
        
        # 自定义回调URL
        if (isset($payInfo['notify_url'])) {
            $this->notifyUrl = $payInfo['notify_url'];
        }

        # 自定义参数- 用于判断客户
        $attach = base64_encode("id={$dbInfo['admin_id']}");

        # 统一下单
        $input = new \WxPayUnifiedOrder();
        $input->SetBody($dbInfo['subject']);
        $input->SetAttach($attach);                                // 自定义参数
        $input->SetOut_trade_no($orderId);
        $input->SetTotal_fee($dbInfo['amount'] * 100);             // 计算总价 - 单位为分
        $input->SetTime_start(date("YmdHis"));
        $input->SetTime_expire(date("YmdHis", time() + 600));
        $input->SetGoods_tag("Applet");
        $input->SetTrade_type("JSAPI");
        $input->SetOpenid($openid);
        $input->SetNotify_url($this->notifyUrl);
        $order = \WxPayApi::unifiedOrder($input);
        
        if ($order['return_code'] == 'FAIL') {
            err($order['return_msg'], $order);
        }

        if ($order['result_code'] == 'FAIL') {
            err($order['return_msg'], $order);
        }

        # 订单信息写入数据库 - 未付款的订单不需要再创建订单数据
        if (!isset($payInfo['not_create_order'])) {
            // 默认为单商户
            $dbInfo['param']['type'] = 'single_store';
            
            $dbInfo['param'] = json_encode($dbInfo['param']);
            $dbInfo['type'] = isset($dbInfo['type']) ? $dbInfo['type'] : 2;  // 1：货到付款，2：微信支付，3：支付宝支付;
            $dbInfo['openid'] = $openid;
            $dbInfo['out_trade_no'] = $orderId;
            $dbInfo['message'] = isset($dbInfo['message']) ? $dbInfo['message'] : '';
            $dbInfo['time_start'] = date("Y-m-d H:i:s");
            $dbInfo['time_expire'] = date("Y-m-d H:i:s", time() + 600);
            db('order')->insert($dbInfo);
        }
        
        # 调用再次签名
        $sign = new \WxPayJsApiPay();
        $sign->SetAppid(CUS_WX_APPID);
        $sign->SetTimeStamp($this->timeStamp);
        $sign->SetNonceStr(getRandStr(20));
        $sign->SetPackage('prepay_id=' . $order['prepay_id']);
        $sign->SetSignType('MD5');

        # 返回再次签名参数
        $param = [];
        $param['timeStamp'] = $sign->GetTimeStamp();
        $param['nonceStr'] = $sign->GetReturn_code();
        $param['package'] = $sign->GetPackage();
        $param['signType'] = $sign->GetSignType();
        $param['paySign'] = $sign->MakeSign();          // 生成签名
        $param['orderId'] = $orderId;
        return $param;
    }
    
    /**
     * 在微信系统中查询订单信息
     * @param string $admin_id  客户ID
     * @param string $out_trade_no  商户定义的订单ID
     * 
       array(10) {
          ["appid"] => string(18) "wx6d244d9539247612"
          ["mch_id"] => string(10) "1459519502"
          ["nonce_str"] => string(16) "eiFkqaZz6GAUggKO"
          ["out_trade_no"] => string(24) "201711291246301065176529"
          ["result_code"] => string(7) "SUCCESS"
          ["return_code"] => string(7) "SUCCESS"
          ["return_msg"] => string(2) "OK"
          ["sign"] => string(32) "51D8DF3886657F4BC8719D6A0598ECE6"
          ["trade_state"] => string(6) "NOTPAY"
          ["trade_state_desc"] => string(15) "订单未支付"
        }
     */
    public function wxQueryOrder($admin_id, $out_trade_no)
    {
        require_once VENDOR_PATH . 'Wxpay/lib/WxPay.Api.php';
        
        # 引入配置参数
        self::wxPayConfig($admin_id);
        
        $input = new \WxPayOrderQuery();
        $input->SetOut_trade_no($out_trade_no);
        
        $res = \WxPayApi::orderQuery($input);
        
        if ($res['result_code'] != 'SUCCESS') {
            err($res['err_code_des'], $res);
        }
        
        // 如果微信系统中查询到订单信息，请根据trade_state来判断订单状态
        return $res;
    }
    
    /**
     * 微信支付回调处理函数 - notify_url - 不需要入口验证
     * 测试内容
     *
       <xml>
         <appid><![CDATA[wx6d244d9539247612]]></appid>
         <attach><![CDATA[aWQ9MTI3Nw==]]></attach>
         <bank_type><![CDATA[CFT]]></bank_type>
         <cash_fee><![CDATA[1]]></cash_fee>
         <fee_type><![CDATA[CNY]]></fee_type>
         <is_subscribe><![CDATA[N]]></is_subscribe>
         <mch_id><![CDATA[1459519502]]></mch_id>
         <nonce_str><![CDATA[r8uff0db6c4zevn9x00cowmx6d7e0my5]]></nonce_str>
         <openid><![CDATA[o8aD50FXqUa3dLU3TWaTativj_Dw]]></openid>
         <out_trade_no><![CDATA[201705251044301275153001]]></out_trade_no>
         <result_code><![CDATA[SUCCESS]]></result_code>
         <return_code><![CDATA[SUCCESS]]></return_code>
         <sign><![CDATA[14A8EEECD6D4DB51894873004B13D163]]></sign>
         <time_end><![CDATA[20170525104456]]></time_end>
         <total_fee>1</total_fee>
         <trade_type><![CDATA[JSAPI]]></trade_type>
         <transaction_id><![CDATA[4002342001201705252486403712]]></transaction_id>
       </xml>

     * @param number $admin_id
     */
    public function wxNotify($admin_id) {
        // 引入配置参数
        self::wxPayConfig($admin_id);

        $notify = new WxNotify();
        $notify->Handle(false);
    }

    /**
     * 获取access token
     * 
     * @param number $admin_id
     * @return array
     */
    public function getAccessToken($admin_id) {
        $res = db('customer')->where(['id' => $admin_id])->field('id, appid, secret, access_token, access_expires')->find();

        if (!$res) {
            return false;
        }

        // 不存在或者过期，就重新获取access token
        if (empty($res['access_token']) or $res['access_expires'] < $this->timeStamp) {
            $url = 'https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential' .
                '&appid=' . $res['appid'] .
                '&secret=' . $res['secret'];

            $getMess = file_get_contents($url);
            $arr = json_decode($getMess, true);

            if (isset($arr['errcode'])) {
                return false;
            }

            // 更新access_token
            $data = [];
            $data['access_token'] = $arr['access_token'];
            $data['access_expires'] = $this->timeStamp + $arr['expires_in'];

            $result = db('customer')->where(['id' => $admin_id])->update($data);

            return $data;
        }

        return $res;
    }

    /**
     * 发送模板消息
     * @param array $data
     * @return json
     */
    public function sendTemplateMsg($data) {
        $url = 'https://api.weixin.qq.com/cgi-bin/message/wxopen/template/send?access_token=' . $data['access_token'];

        // 发送的数据
        $sendData = [];
        $sendData['touser'] = $data['openid'];
        $sendData['template_id'] = $data['template_id'];    // 所需下发的模板消息的id
        $sendData['form_id'] = $data['form_id'];            // 表单提交场景下，为 submit 事件带上的 formId；支付场景下，为本次支付的 prepay_id

        if (!empty($data['page'])) {
            $sendData['page'] = $data['page'];              // 跳转仅限本小程序内的页面，支持带参数
        }

        if (!empty($data['data'])) {
            $sendData['data'] = $data['data'];              // 模板内容，不填则下发空模板
        } else {
            $sendData['data'] = '';
        }

        $sendData = json_encode($sendData);
        $res = curlPost($url, $sendData, 'json');               // 返回的数据 - json格式

        $res = json_decode($res, true);

        return $res;
    }

    /**
     * 解密wx.userinfo的加密信息 - 获取openid, unionId等
     * @param array $info
     *
     * @param number $appid appid
     * @param number $session_key session_key
     * @param number $encrypted_data wx.userinfo.encryptedData
     * @param number $iv wx.userinfo.iv
     *
     * @return json
     */
    public function decryptUserinfo($info) {
        require_once VENDOR_PATH . 'WxAes/wxBizDataCrypt.php';

        /* 
        $appid = 'wx4f4bc4dec97d474b';
        $sessionKey = 'tiihtNczf5v6AKRyjwEUhQ==';
        
        $encryptedData="CiyLU1Aw2KjvrjMdj8YKliAjtP4gsMZM
                QmRzooG2xrDcvSnxIMXFufNstNGTyaGS
                9uT5geRa0W4oTOb1WT7fJlAC+oNPdbB+
                3hVbJSRgv+4lGOETKUQz6OYStslQ142d
                NCuabNPGBzlooOmB231qMM85d2/fV6Ch
                evvXvQP8Hkue1poOFtnEtpyxVLW1zAo6
                /1Xx1COxFvrc2d7UL/lmHInNlxuacJXw
                u0fjpXfz/YqYzBIBzD6WUfTIF9GRHpOn
                /Hz7saL8xz+W//FRAUid1OksQaQx4CMs
                8LOddcQhULW4ucetDf96JcR3g0gfRK4P
                C7E/r7Z6xNrXd2UIeorGj5Ef7b1pJAYB
                6Y5anaHqZ9J6nKEBvB4DnNLIVWSgARns
                /8wR2SiRS7MNACwTyrGvt9ts8p12PKFd
                lqYTopNHR1Vf7XjfhQlVsAJdNiKdYmYV
                oKlaRv85IfVunYzO0IKXsyl7JCUjCpoG
                20f0a04COwfneQAGGwd5oa+T8yO5hzuy
                Db/XcxxmK01EpqOyuxINew==";
        
        $iv = 'r7BXXKkLb8qrSNn05n0qiA==';
        
        $pc = new \WXBizDataCrypt($appid, $sessionKey);
        $errCode = $pc->decryptData($encryptedData, $iv, $data);
        var_dump($data);
        die; 
        */

        // 获取sessionKey
        $where = ['id' => $info['user_id'], 'admin_id' => $info['admin_id']];
        $field = 'session_key, unionid';
        $user = db('user')->field($field)->where($where)->find();

        $pc = new \WXBizDataCrypt($info['appid'], $user['session_key']);
        $errCode = $pc->decryptData($info['encrypted_data'], $info['iv'], $data);

        if ($errCode != 0) {
            err('decrypt fail', $errCode);
        }

        // 更新unionid
        if (strlen($user['unionid']) <= 0) {
            db('user')->where($where)->update(['unionid' => $data['unionId']]);
        }

        suc('success', $data);
    }

    /**
     * 获取二维码 - B方案: https://mp.weixin.qq.com/debug/wxadoc/dev/api/qrcode.html
     * 接口B：适用于需要的码数量极多，或仅临时使用的业务场景
     * 
     * @param integer $adminId      客户ID
     * @param string  $userId       用户ID
     * @param string  $scene        携带的参数
     * @param string  $page         发布的小程序页面，默认为pages/index/index
     * @param number  $width        二维码的宽度，默认为200
     * @return string   返回图片路径
     */
    public function getRqcodeB($adminId, $userId, $scene, $page = 'pages/index/index', $width = 200) {
        $access = $this->getAccessToken($adminId);
        $url = 'http://api.weixin.qq.com/wxa/getwxacodeunlimit?access_token=' . $access['access_token'];
        
        // 存放路径
        $root = ROOT_PATH . 'public' . DS;
        $uploads = 'uploads' . DS . $adminId . DS . 'rqcode' . DS;

        // 文件名称 
        $fileName = $userId . 'B.jpg';
        
        // 文件存在就不下载
        if (file_exists($root . $uploads . $fileName)) {
            return $uploads . $fileName;
        } else {
            @mkdir($root . $uploads, 0777, true);
        }

        $resData = [];
        $resData['scene'] = $scene;
        $resData['page'] = $page;
        $resData['width'] = $width;
        $resData['auto_color'] = true;
        //$resData['line_color'] = '{"r":"0","g":"0","b":"0"}';
        $resData = json_encode($resData);
        $response = curlPost($url, $resData, 'json');

        // 把微信返回的数据生成二维码
        file_put_contents($root . $uploads . $fileName, $response);

        return $uploads . $fileName;
    }

    /**
     * 获取二维码 C方案
     * @param number $admin_id      客户ID
     * @param string  $userId       用户ID
     * @param string $path          小程序跳转路径(包含参数) - 默认为首页 pages/index/index?scene=123
     * @param number $width         二维码宽度
     * @return string   返回图片路径
     */
    public function getRqcodeC($adminId, $userId, $wxPath, $width = 200) {
        // 获取Access Token
        $access_token = $this->getAccessToken($adminId);
        $url = 'https://api.weixin.qq.com/cgi-bin/wxaapp/createwxaqrcode?access_token=' . $access_token['access_token'];
        
        // 存放路径
        $root = ROOT_PATH . 'public' . DS;
        $uploads = 'uploads' . DS . $adminId . DS . 'rqcode' . DS;

        // 文件名称 
        $fileName = $userId . 'C.jpg';
        
        // 文件存在就不下载
        if (file_exists($root . $uploads . $fileName)) {
            return $uploads . $fileName;
        } else {
             @mkdir($root . $uploads, 0777, true);
        }
        
        $resData = [];
        $resData['path'] = $wxPath;       // pages/index/index?scene=123
        $resData['width'] = $width;
        $resData = json_encode($resData);
        $response = curlPost($url, $resData, 'json');
        
        // 把微信返回的数据生成二维码
        file_put_contents($root . $uploads . $fileName, $response);

        return $uploads . $fileName;
    }

}